/**
 *
 *embustest main process.
 *
 * @embustest.cpp
 *
 * @remarks
 * 2022 created by yy, 2023 rewrite by ycl.
 */

#include <stdio.h>
#include <iostream>
#include <chrono>
#include <thread>
#include <cstring>
#include <map>
#include <future>
#include <sys/time.h>
#include "libembus.h"
#include "packet.h"
#include "cmdline.h"
#include "msgproctest.h"
#include "libemfact.h"
#include "remote_cmd.h"

using namespace std;
#define VER_STR "version: 4.0.3 (2023-8-11 10:37:33)\n"

/* global vars & defines */
static enum msg_rx_action
{
    null_action,
    can_show,
    lin_show,
    vio_show,
    com_show,
    event_show,
    gps_show,
    state_show,
    state_get,
    factory_test,
    peer_factory_tester,
    remote_cmd
} g_action_type = null_action;
#define is_action(type) (type == g_action_type ? true:false)
#define set_action(type) g_action_type = type

static bool g_show_debug_en = false;     /* debug option for CAN rx details output*/
#define is_show_debug() g_show_debug_en
#define set_show_debug() g_show_debug_en = true 
#define clear_show_debug() g_show_debug_en = false 

static string g_channel;      /* for save the channel value input by the terminal */
FILE *fp = NULL;              /* log file */

/** 
******************************************************************************** 
 * @name  msg_rx_proc
 * @param pkt_id    packet id
 * @param data      packet data
 * @param data_len  packet data len
 * @param param     param, which you embus_connect carried
 * @return   NA
 * @remarks  embus rx msg callback
 ******************************************************************************** 
*/
void msg_rx_proc(unsigned int pkt_id, unsigned char *data, int data_len, void *param)
{    
#if 0
    printf("message id = 0x%x received, length = %d\n", pkt_id, data_len);
    for(int i=0; i<data_len; ++i)
    {
        printf("%02x ", data[i]);
    }
    printf("\n");
#endif 
    
    if(is_action(factory_test)) FACT_msgProc(pkt_id, data, data_len);
    if(is_action(peer_factory_tester)) FACP_msgProc(pkt_id, data, data_len);
    if(is_action(remote_cmd)) EMRC_msgProc(pkt_id, data, data_len);

#if 1
    /* Sub CAN/LIN Message and ADC/DI/DO State */
    if (pkt_id == EMBUS_BMR_DAT_ID)
    {
        struct com_packet* pac = (struct com_packet*)data;
        /* show com_frame data -- can/canfd or lin*/ 
        if ((pac->id != 0) && (pac->id != 0xFFFFFFFF) && (pac->id != 0XFFFFFFFE) && (data_len == sizeof(struct com_packet))) 
        {
            if (is_action(can_show) && (((pac->channel & 0xC0) >> 6)==0x00 || ((pac->channel & 0xC0) >> 6)==0x01)) 
            {
                if(is_show_debug()){
                    show_can_com_frame_msg(pac, g_channel, fp);                
                }
                update_can_com_framemap_info(pac);                
            }     
            if (is_action(lin_show) && (((pac->channel & 0xC0) >> 6)==0x02)) 
            {
                show_lin_com_frame_msg(pac, g_channel, fp);
            }   
        }
        /* show vio status */
        if (is_action(vio_show) && (pac->id == 0) && (data_len==sizeof(struct vio_status)))
        {
            struct vio_status* vio_pac = (struct vio_status*)data;
            show_vio_status_msg(vio_pac, g_channel);
        }
        /* show com status */
        if (is_action(com_show) && (pac->id == 0xFFFFFFFF) && 
            (data_len==sizeof(struct com_status)))
        {
            struct com_status* com_pac = (struct com_status*)data;
            show_com_status_msg(com_pac, g_channel);
        } 
        /* show com event */
        if (is_action(event_show) && (pac->id == 0xFFFFFFFE) && (data_len==sizeof(struct com_event)))
        {
            struct com_event* event_pac = (struct com_event*)data;
            show_com_event_msg(event_pac, g_channel); 
        }
    }

    /* Sub EMBUS_GPS_DAT_ID and GPS data */
    if (pkt_id == EMBUS_GPS_DAT_ID) 
    {
        struct gps_data* pac = (struct gps_data*)data;
        /* show gps data */
        if (is_action(gps_show) && (data_len==sizeof(struct gps_data)))
        {
            show_gps_data_msg(pac); 
        } 
    }

    /* Sub ADC/DI/DO/LED state data */
    if (pkt_id == EMBUS_STATUS_ID) 
    {
        struct rmu_state* rmustate_pac = (struct rmu_state*)data;
        /* show state */
        if (is_action(state_show) && (data_len==sizeof(struct rmu_state)))
        {
            show_state_data_msg(rmustate_pac); 
        } 

        /* get state */
        if (is_action(state_get) && (data_len==sizeof(struct rmu_state)))
        {
            get_state_data_msg(rmustate_pac); 
        }
    }
#endif
}

int main(int argc, char *argv[])
{
    int ret;
    int txloop, txinterval;
    string txstr_ch,txstr_frame;
    std::future<int>  f_ret;
    embus_handle_t *h;
    cmdline::parser emcmd;
    uint32_t subscribed_pkt_ids[4] = {EMBUS_BMR_DAT_ID, EMBUS_VCI_RESP_ID, EMBUS_GPS_DAT_ID, EMBUS_STATUS_ID};
    emcmd.add("version" ,       '\0', "embustest version");
    emcmd.add("candump" ,       '\0', "Get CAN message:       [--debug]");
    emcmd.add("lindump" ,       '\0', "Get LIN message");
    emcmd.add("viostatusdump",  '\0', "Get viostatus message");
    emcmd.add("comstatusdump",  '\0', "Get comstatus message");
    emcmd.add("eventdump",      '\0', "Get event message");
    emcmd.add("busoff",         '\0', "Bus off test");
    emcmd.add("cansend",        '\0', "Send CAN message:      <--ch, --frame, --loop, --interval>[--debug]");    
    emcmd.add("canconfig",      '\0', "Configure CAN channel: <--baudrate, --dbaudrate, --sample, --dsample>");    
    emcmd.add("cansetmode",     '\0', "Set CAN channel mode:  <--ch, --mode>");    
    emcmd.add("linconfig",      '\0', "Configure LIN channel: <--ch, --rate>");    
    emcmd.add("linsetmode",     '\0', "Set LIN channel mode:  <--ch, --mode>");
    emcmd.add("DoipPowerOnInitSet",        '\0', "DoIP Active Line power-on initialization settings");
    emcmd.add("DoipSet",        '\0', "DoIP Active Line settings: <--state>");        
    emcmd.add("rtcwkconfig",    '\0', "Set RTC wake-up time:  <--wktime, --wkmode>");    
    emcmd.add("DOconfig",       '\0', "Set DO output state:   <--doctl, --dosta>");    
    emcmd.add("ledconfig",      '\0', "Set Led:               <--ledctl, --ledsta>");    
    emcmd.add("buzzerconfig",   '\0', "Set Buzzer:            <--buzsta>");
    emcmd.add("sleep",          '\0', "Device sleep");
    emcmd.add("gpsdump",        '\0', "Subscribe GPS data");
    emcmd.add("statedump",      '\0', "Show device state info");
    emcmd.add("factorytest",    '\0', "Factory test");
    emcmd.add("peerfactorytester",   '\0', "Peer factory tester:   [--debug]");
	emcmd.add("otatest",        '\0', "K3 ota test");    
    emcmd.add("remotecmd",      '\0', "Remote test and use");
    emcmd.add<string>("ch",     '\0', "[param]: CAN channel(1-16) --ch=1~16", false, "");
    emcmd.add<string>("frame",  '\0', "[param]: CAN data --frame=id#type#dlc#data", false, "");
    emcmd.add<int>("loop",      '\0', "[param]: Send CAN message count", false, 100000);
    emcmd.add<int>("interval",  '\0', "[param]: Send CAN message interval, unit: ms", false, 100);
    emcmd.add<int>("baudrate",  '\0', "[param]: CAN baudrate, 250Kbps 500Kbps...", false, 500000);
    emcmd.add<int>("dbaudrate", '\0', "[param]: CANFD baudrate, 1Mbps 2Mbps...", false, 1000000);
    emcmd.add<int>("sample",    '\0', "[param]: CAN sample 75% 80% ...", false, 80, cmdline::range(1, 100));
    emcmd.add<int>("dsample",   '\0', "[param]: CANFD sample 80% ...", false, 80, cmdline::range(1, 100));
    emcmd.add<string>("mode",   '\0', "[param]: CAN work mode, 0 - disable; 1- normal; 2- listen only", false, "");
    emcmd.add<int>("rate",      '\0', "[param]: lin's communication rate, 4800 9600 ...", false, 0, cmdline::range(0, 65535));
    emcmd.add<int>("state",     '\0', "[param]: DoIP Active Line state, 0- Low , 1- High", false, 0, cmdline::range(0, 1));
    emcmd.add<string>("fp",     '\0', "[param]: Save log path", false, "");
    emcmd.add<string>("wktime", '\0', "[param]: RTC wake-up time day-hour:minute:second", false, "");
    emcmd.add<int>("wkmode",    '\0', "[param]: RTC wake-up mode  0- month, 1- week", false, 0, cmdline::range(0, 1));
    emcmd.add<int>("buzsta",    '\0', "[param]: buzzer state, 0-close, 1-open", false, 0, cmdline::range(0, 65535));
    emcmd.add<string>("doctl",  '\0', "[param]: DO contrl: DO3,DO2,DO1,DO0, 1- contrl, 0- no contrl", false, "");
    emcmd.add<string>("dosta",  '\0', "[param]: DO state: DO3,DO2,DO1,DO0, 1- set value 1, 0- set value 0", false, "");
    emcmd.add<string>("ledctl", '\0', "[param]: LED1~LED18 control enable, 0-close, 1-slow flash, 2-flash, 3-open", false, "");
    emcmd.add<string>("ledsta", '\0', "[param]: LED1~LED16 control options", false, "");
    emcmd.add<int>("debug",   '\0', "[option]: --debug=1,for some commands debug detail output", false, 0, cmdline::range(0, 1));
    emcmd.parse_check(argc, argv);
    if (argc==1 || emcmd.exist("help"))
    {
        printf(VER_STR);
        cerr << emcmd.usage();
        return 0;
    }
    if (emcmd.exist("version"))
    {
        printf(VER_STR);
        return 0;
    }
#if LOGGING_FLAG
    string str_fp = emcmd.get<string>("fp"); 
    if (str_fp.empty()) {
        fp = fopen("./test.log", "w+");     /* default log file open*/  
    }
    else {
        fp = fopen(str_fp.c_str(), "w+");   /* user input log file open*/  
    }  
#endif /* LOGGING_FLAG */   
    /*    */   
    h = embus_connect("/tmp/embus", "demo", msg_rx_proc, NULL);
    if (h != NULL)
    {
        embus_msg_subscribe(h, subscribed_pkt_ids, 4);
        //embus_msg_subscribe(h, subscribed_pkt_ids1, 1);
        if (emcmd.exist("candump"))
        {
            set_action(can_show);
            int debug = emcmd.get<int>("debug");
            if(1 == debug) set_show_debug();
            g_channel = emcmd.get<string>("ch");
#if LOGGING_FLAG
            fprintf(fp, "count\ttimestamp\tcanid\tchannel\tdir\ttype\tflag\tdlc\tdata\n");  /* logging */ 
            printf("logging...\n");  
#else
            printf("count\ttimestamp\tcanid\tchannel\tdir\ttype\tflag\tdlc\tdata\n");   
#endif /* LOGGING_FLAG */ 
        }
        else if (emcmd.exist("lindump"))
        {
            set_action(lin_show);
            g_channel = emcmd.get<string>("ch");
#if LOGGING_FLAG
            fprintf(fp, "count\ttimestamp\tlinid\tchannel\tdir\ttype\tflag\tdlc\tdata\n");  /* logging */ 
            printf("logging...\n");  
#else
            printf("count\ttimestamp\tlinid\tchannel\tdir\ttype\tflag\tdlc\tdata\n");
#endif /* LOGGING_FLAG */ 
        }
        else if (emcmd.exist("viostatusdump"))
        {
            set_action(vio_show);
            g_channel = emcmd.get<string>("ch");
            printf("rxcount\ttimestamp\tchannel\t4GM_3V8_ADC\tSYS_2V5_ADC\tDoIP_Active_DAIN\tHS_Temperature\n");
        }
        else if (emcmd.exist("comstatusdump"))
        {
            set_action(com_show);
            g_channel = emcmd.get<string>("ch");
            printf("rxcount\ttimestamp\ttype\tchannel\ttime_interval\tbus_event_count\trx_packet_count\ttx_packet_count\tbus_loader\n");
        }
        else if (emcmd.exist("eventdump"))
        {
            set_action(event_show);
            g_channel = emcmd.get<string>("ch");
            printf("rxcount\ttimestamp\ttype\tchannel\tcan_controller_sate\tcan_bus_state\tREC\tTEC\tERRINFO\n");
        }
        else if (emcmd.exist("gpsdump"))
        {
            set_action(gps_show);
        }
        else if (emcmd.exist("statedump"))
        {
            set_action(state_show);
        }
        else if(emcmd.exist("factorytest"))
        {
            int debug = emcmd.get<int>("debug");
            set_action(factory_test);
            ret = FACT_run(h, debug);
        }
        else if(emcmd.exist("peerfactorytester"))
        {
            int debug = emcmd.get<int>("debug");
            set_action(peer_factory_tester);
            ret = FACP_run(h, debug);
        }
        else if (emcmd.exist("busoff")) /* bus-off test */
        {
            set_action(com_show);
            int loop = emcmd.get<int>("loop");
            int interval = emcmd.get<int>("interval");
            string str_ch = emcmd.get<string>("ch");  
            string str_frame = emcmd.get<string>("frame");
            g_channel = str_ch;                   
            printf("rxcount\ttimestamp\ttype\tchannel\ttime_interval\tbus_event_count\trx_packet_count\ttx_packet_count\tbus_loader\n");
            ret = msg_tx_canmsg_test(h, str_ch, loop, interval, str_frame);
        }  
        else if (emcmd.exist("cansend"))
        {
            set_action(can_show);
            int debug = emcmd.get<int>("debug");
            if(1 == debug) set_show_debug();          
            txloop = emcmd.get<int>("loop");
            txinterval = emcmd.get<int>("interval");
            txstr_ch = emcmd.get<string>("ch");  
            txstr_frame = emcmd.get<string>("frame");
            printf("count\ttimestamp\t\tcanid\tchannel\tdir\ttype\tflag\tdlc\tdata\n");
            f_ret = std::async(std::launch::async,[h,txloop,txinterval,txstr_ch,txstr_frame](){
              msg_tx_canmsg_test(h, txstr_ch, txloop, txinterval, txstr_frame);
              return 0;
            });      
        } 
        else if (emcmd.exist("canconfig"))
        {
            string str_ch = emcmd.get<string>("ch");
            uint8_t sample = (uint8_t)emcmd.get<int>("sample");
            uint8_t dsample = (uint8_t)emcmd.get<int>("dsample");
            uint32_t baudrate = (uint32_t)emcmd.get<int>("baudrate");
            uint32_t dbaudrate = (uint32_t)emcmd.get<int>("dbaudrate");
            ret = msg_tx_canconfig_test(h, str_ch, baudrate, dbaudrate, sample, dsample);
            if (ret == 0)
                printf("CAN interface communication setting successfully! Press CTRL+C to end the program!\n");
            else
                printf("CAN interface communication setting failed! Press CTRL+C to end the program!\n");
        } 
        else if (emcmd.exist("cansetmode"))
        {
            uint8_t set_mode;
            string str_ch = emcmd.get<string>("ch");
            string str_mode = emcmd.get<string>("mode");
            if (str_mode == "disable") set_mode = 0;            
            else if (str_mode == "normal") set_mode = 1;
            else if (str_mode == "listen") set_mode = 2;
            else
            {
                printf(VER_STR);
                cerr << emcmd.usage();
                return 0;                
            }
            ret = msg_tx_cansetmode_test(h, str_ch, set_mode);
            if (ret == 0)
                printf("CAN interface mode setting successfully! Press CTRL+C to end the program!\n");
            else
                printf("CAN interface mode setting failed! Press CTRL+C to end the program!\n");
        }
        else if (emcmd.exist("linconfig"))
        {
            string str_ch = emcmd.get<string>("ch");
            uint16_t rate = (uint16_t)emcmd.get<int>("rate");
            ret = msg_tx_linconfig_test(h, str_ch, rate); 
            if (ret == 0)
                printf("Lin interface communication setting successfully! Press CTRL+C to end the program!\n");
            else
                printf("Lin interface communication setting failed! Press CTRL+C to end the program!\n");
        } 
        else if (emcmd.exist("linsetmode"))
        {
            uint8_t set_mode;
            string str_ch = emcmd.get<string>("ch");
            string str_mode = emcmd.get<string>("mode");
            if (str_mode == "disable") set_mode = 0;            
            else if (str_mode == "normal") set_mode = 1;
            else if (str_mode == "listen") set_mode = 2;
            else
            {
                printf(VER_STR);
                cerr << emcmd.usage();
                return 0;                
            }
            ret = msg_tx_linsetmode_test(h, str_ch, set_mode);
            if (ret == 0)
                printf("Lin interface mode setting successfully! Press CTRL+C to end the program!\n");
            else
                printf("Lin interface mode setting failed! Press CTRL+C to end the program!\n");           
        }
        else if (emcmd.exist("DoipPowerOnInitSet"))
        {
            int DoIP_state = emcmd.get<int>("state");
            ret = msg_tx_DoipPowerOnInitSet_test(h, DoIP_state);
            if (ret == 0)
                printf("DoIP Active Line Power on Init setup successfully! Press CTRL+C to end the program!\n");
            else
                printf("DoIP Active Line Power on Init setup failed! Press CTRL+C to end the program!\n");
        } 
        else if (emcmd.exist("DoipSet"))
        {
            int DoIP_state = emcmd.get<int>("state");
            ret = msg_tx_DoipSet_test(h, DoIP_state);
            if (ret == 0)
                printf("DoIP Active Line setup successfully! Press CTRL+C to end the program!\n");
            else
                printf("DoIP Active Line setup failed! Press CTRL+C to end the program!\n");
        }
        else if (emcmd.exist("rtcwkconfig"))
        {
            string str_wktime = emcmd.get<string>("wktime");
            uint8_t wk_mode = (uint8_t)emcmd.get<int>("wkmode");
            ret = msg_tx_RTCwakeup_test(h, str_wktime, wk_mode);
            if (ret == 0)
                printf("Set RTC wake-up time successfully! Press CTRL+C to end the program!\n");
            else
                printf("Failed to set RTC wakeup time! Press CTRL+C to end the program!\n");
        } 
        else if (emcmd.exist("DOconfig"))
        {
            string str_DOctl = emcmd.get<string>("doctl");
            string str_DOstate = emcmd.get<string>("dosta");
            ret = msg_tx_DOstate_test(h, str_DOctl, str_DOstate);
            if (ret == 0)
                printf("Control DO output status successfully! Press CTRL+C to end the program!\n");
            else
                printf("Failed to control DO output state! Press CTRL+C to end the program!\n");
        } 
        else if (emcmd.exist("ledconfig"))
        {
            string str_LEDctl = emcmd.get<string>("ledctl");
            string str_LEDstate = emcmd.get<string>("ledsta");
            ret = msg_tx_LEDctrl_test(h, str_LEDctl, str_LEDstate);
            if (ret == 0)
                printf("Set LED control output successfully! Press CTRL+C to end the program!\n");
            else
                printf("Failed to set LED control output! Press CTRL+C to end the program!\n");
        } 
        else if (emcmd.exist("buzzerconfig"))
        {
            uint32_t str_BUZstate = (uint32_t)emcmd.get<int>("buzsta");
            ret = msg_tx_BUZctrl_test(h, str_BUZstate);
            if (ret == 0)
                printf("Set BUZZER status successfully! Press CTRL+C to end the program!\n");
            else
                printf("Failed to set BUZZER status! Press CTRL+C to end the program!\n");
        } 
        else if (emcmd.exist("sleep"))
        {
            /* get ign on state first */
            set_action(state_get);
            while(0xff == get_ign_state()){
                std::this_thread::sleep_for(std::chrono::milliseconds(10));
            }
            if(1 == get_ign_state()){
                printf("IGN on, can't sleep! Press CTRL+C to end the program!\n");
            }
            else
            {
                ret = msg_tx_sleep_enable_test(h, "0xFF");
                if (ret == 0)
                    printf("Sleep enabled successfully! Press CTRL+C to end the program!\n");
                else
                    printf("Sleep enable failed! Press CTRL+C to end the program!\n");
            }
        } 
        else if (emcmd.exist("otatest"))
        {
            ret = msg_tx_ota_test(h, 1);
            if (ret == 0)
                printf("Set k3 oat status successfully! Press CTRL+C to end the program!\n");
            else
                printf("Set k3 oat status failed! Press CTRL+C to end the program!\n");
        }
        else if(emcmd.exist("remotecmd"))
        {
            int debug = emcmd.get<int>("debug");
            set_action(remote_cmd);
            ret = EMRC_run(h, debug);
        }                                                                                                            
        else
        {
            printf(VER_STR);
            cerr << emcmd.usage();
            return 0;
        }
        int seconds = 0;
        while (1)
        {
            std::this_thread::sleep_for(std::chrono::milliseconds(1000));
            seconds ++;
            if (emcmd.exist("cansend") || emcmd.exist("candump"))
            {
                if ((seconds % 3) == 0)
                {
                    show_can_com_framemap_info();
                }
            }
        }  
        embus_disconnect(h);
    }
    else
    {
        printf("embus_connect() fail.\n");
    }
#if LOGGING_FLAG    
    fclose(fp);  /* log file close*/ 
#endif /* LOGGING_FLAG */  
    /*  */
    return 0;
}

/* Config response */
/*
    if (pkt_id == EMBUS_VCI_RESP_ID) 
    {
        struct em_rsponse* rsp = (struct em_rsponse*)data;
        if (rsp->command == 0x81)        // write response
        {
            if (rsp->param == 0)        // can
            {
                for (int i=0; i<12; ++i) {
                    printf("%02X ", rsp->data[i]);
                }
                printf("\n");
                // if (rsp->data[0] == 0)
                //     printf("Send CAN messages succeed! Channel: %u\n", rsp->channel);
                // else 
                //     printf("Send CAN messages failed! Channel: %u\n", rsp->channel);
            }
            else if (rsp->param == 1)   // lin
            {
                for (int i=0; i<12; ++i) {
                    printf("%02X ", rsp->data[i]);
                }
                printf("\n");
                // if (rsp->retdata == 0)
                //     printf("Send CANFD messages succeed! Channel: %u\n", rsp->channel);
                // else
                //     printf("Send CANFD messages failed! Channel: %u\n", rsp->channel);          
            }
            else
            {
                printf("Write messages error!\n");
            }     
        }
        else if (rsp->command == 0x82)   // set response
        {
            if (rsp->protocol == 0x01)  // can
            {
                if (rsp->param == 2)    // can setting
                {
                    for (int i=0; i<12; ++i) {
                        printf("%02X ", rsp->data[i]);
                    }
                    printf("\n");
                    // if (rsp->retdata == 0)
                    //     printf("CAN interface communication setting is successful!\n");
                    // else
                    //     printf("CAN interface communication setting failed!\n");          
                }
                else if (rsp->param == 3)       // can mode
                {
                    for (int i=0; i<12; ++i) {
                        printf("%02X ", rsp->data[i]);
                    }
                    printf("\n");
                    // if (rsp->retdata == 0)
                    //     printf("CAN interface mode setting is successful!\n");
                    // else
                    //     printf("CAN interface mode setting failed!\n"); 
                }
                else 
                {
                    printf("CAN interface setting error!\n"); 
                }   
            }
            else if (rsp->protocol == 0x02)     // lin
            {
                if (rsp->param == 1)            // lin setting
                {
                    // if (rsp->retdata == 0)
                    //     printf("LIN interface communication setting is successful!\n");
                    // else
                    //     printf("LIN interface communication setting failed!\n");          
                }
                else if (rsp->param == 2)       // lin mode
                {
                    // if (rsp->retdata == 0)
                    //     printf("LIN interface mode setting is successful!\n");
                    // else
                    //     printf("LIN interface mode setting failed!\n"); 
                }
                else 
                {
                    printf("LIN interface setting error!\n"); 
                }    
            }
            else
            {
                printf("Set messages error!\n");
            }  
        }
        else
        {
            printf("Subscribe EMBUS_VCI_RESP_ID messages error!\n");
        }   
    }
*/